package com.siteseat.common.spring;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;

/**
 *
 */
public class ExposedBean {

  private ConfigurableApplicationContext context; 
  
  private Class<?> type;

  private String innerName;

  private Set<String> exposedNames;

  
  /**
   * Gets the context
   *
   * @return the context
   */
  public ConfigurableApplicationContext getContext() {
    return context;
  }

  /**
   * Sets the context
   *
   * @param context the context to set
   */
  public void setContext( ConfigurableApplicationContext context ) {
    this.context = context;
  }
  
  public ConfigurableListableBeanFactory getFactory() {
    return getContext().getBeanFactory();
  }

  /**
   * Gets the type
   *
   * @return the type
   */
  public Class<?> getType() {
    if( this.type == null ) {
      this.type = getFactory().getType( getInnerName() );
    }
    return type;
  }

  /**
   * 
   *
   * @return
   */
  public Object get() {
    return getFactory().getBean( getInnerName() );
  }
  

  /**
   * 
   *
   * @return
   */
  public Object get( Object... args ) {
    return getFactory().getBean( getInnerName(), args );
  }


  /**
   * Gets the innerName
   *
   * @return the innerName
   */
  public String getInnerName() {
    return innerName;
  }

  /**
   * Sets the innerName
   *
   * @param innerName the innerName to set
   */
  public void setInnerName( String innerName ) {
    this.innerName = innerName;
  }

  /**
   * Gets the exposedNames
   *
   * @return the exposedNames
   */
  public Set<String> getExposedNames() {
    Set<String> r = this.exposedNames;
    if( r == null ) {
      r = new HashSet<String>(1);
      r.add( getInnerName() );
      this.exposedNames = r;
    }
    return r;
  }

  /**
   * Sets the exposedNames
   *
   * @param exposedNames the exposedNames to set
   */
  public void setExposedNames( Set<String> exposedNames ) {
    this.exposedNames = exposedNames;
  }

  /**
   *
   * @param <A>
   * @param annotationType
   * @return
   */
  public <A extends Annotation> A findAnnotationOnBean( Class<A> annotationType ) {    
    return getFactory().findAnnotationOnBean( getInnerName(), annotationType );
  }

  /**
   *
   * @param name
   * @return
   */
  public String[] getAliases() {
    return getFactory().getAliases( getInnerName() );
  }
  
  /**
   *
   * @param <T>
   * @param requiredType
   * @return
   * @throws BeansException
   */
  @SuppressWarnings( "unchecked" )
  public <T> T get( Class<T> requiredType ) {
    if( requiredType == null ) {
      return (T)get();
    }
    Class<?> mt = getType();
    if( mt != null && mt.isAssignableFrom( requiredType ) ) {
      return (T)get();
    }
    return null;
  }
  
  /**
   * 
   *
   * @param type
   * @param includeNonSingletons
   * @param allowEagerInit
   * @return
   */
  @SuppressWarnings( "unchecked" )
  public boolean isForType( Class tp, boolean includeNonSingletons, boolean allowEagerInit ) {
    Class<?> mt = getType(); 
    if( mt == null ) {
      return false;
    }
    if( mt.isAssignableFrom( tp ) ) {
      return true;
    }
    
    String r = getInnerName();
    for( String n: getFactory().getBeanNamesForType( tp, includeNonSingletons, allowEagerInit ) ) {
      if( n.equals( r ) ) {
        return true;
      }
    }
    return false;
  }
  
  /**
   * 
   * @return
   */
  public boolean isSingleton() {
    return getFactory().containsSingleton( getInnerName() );
  }
  
  /**
   * 
   *
   * @return
   */
  public String[] getDependencies() {
    return getFactory().getDependenciesForBean( getInnerName() );
  }
  
  public String[] getDependentBeans() {
    return getFactory().getDependentBeans( getInnerName() );
  }
  
  public BeanDefinition getMergedBeanDefinition() {
    return getFactory().getMergedBeanDefinition( getInnerName() );
  }
  
  public BeanDefinition getBeanDefinition() {
    return getFactory().getBeanDefinition( getInnerName() );
  }
  
  public Object getSingleton() {
    return getFactory().getSingleton( getInnerName() );
  }
  
  public boolean isCurrentlyInCreation() {
    return getFactory().isCurrentlyInCreation( getInnerName() );
  }
  
  /**
   * 
   *
   * @return
   */
  public boolean isFactoryBean() {
    return getFactory().isFactoryBean( getInnerName() );
  }

  /**
   * 
   *
   * @return
   */
  public boolean isPrototype() {
    return getFactory().isPrototype( getInnerName() );
  }
  
  @SuppressWarnings( "unchecked" )
  public boolean isTypeMatch( Class targetType ) {
    return getFactory().isTypeMatch( getInnerName(), targetType );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean equals( Object obj ) {
    ExposedBean that;
    try {
      that = (ExposedBean)obj;
    }catch( ClassCastException cce ) {
      return false;
    }

    return that.context == this.context && that.innerName.equals( this.innerName );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode() {
    return this.innerName.hashCode();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    return this.context.getId() + "." + this.innerName + "(exposedNames: " + exposedNames + ")";
  }
  
  private static final String ILLEGAL_EXPOSING_EXPRESSION =
    "Invalid exposing bean expression, should be like: <inner name>=<exposed name 1>,<exposed name 2>,...,<exposed name n>";
    
  /**
   * 
   *
   * @return
   */
  public static ExposedBean parse( String exposingExpression ) {
    String innerName;
    String[] exposedNames;
    
    int pos = exposingExpression.indexOf( '=' );
    if( pos > 0 ) {    
      innerName = exposingExpression.substring( 0, pos );
      exposedNames = exposingExpression.substring( pos + 1 ).split( "," );
    } else if (pos < 0){
      innerName = exposingExpression;
      exposedNames = new String[] { exposingExpression };
    } else {
      throw new IllegalArgumentException( ILLEGAL_EXPOSING_EXPRESSION + ":\n" + exposingExpression );
    }

    ExposedBean r = new ExposedBean();
    r.setInnerName( innerName );
    r.setExposedNames( new HashSet<String>( Arrays.asList( exposedNames ) ) );
    
    return r;
  }
  
  /**
   *
   * @param exposingExpressions
   * @return
   */
  public static List<ExposedBean> parse( Collection<String> exposingExpressions ) {
    List<ExposedBean> r = new ArrayList<ExposedBean>( exposingExpressions.size() );
    for( String el: exposingExpressions ) {
      ExposedBean b = parse( el );
      r.add( b );
    }
    return r;
  }

}
